编译时注解详解及实现ButterKnife
PS:人是一种很乐于接受自我暗示的生物,你给了自己消极暗示,那么你很容易变得颓废,如果你给了自己积极暗示,那么你也会变得积极起来。
今天看一下编译时注解的相关知识,相信手动实践后你会更容易理解像 Dagger、ARouter、ButterKnife 等这种使用了编译时注解的框架,也更容易理解其内部源码实现,内容如下:
编译时和运行时注解
注解处理器APT
AbstractProcessor
Element和Elements
自定义注解处理器
使用自定义注解处理器
编译时和运行时注解
先了解一下编译时和运行时的区别:
编译时:指编译器将源代码翻译成机器能够识别的代码的过程,Java 中也就是将 Java 源代码编译为 JVM 识别的字节码文件的过程。
运行时:指 JVM 分配内存、解释执行字节码文件的过程。
元注解 @Retention
决定注解是在编译时还是在运行时,其可配置策略如下:
public enum RetentionPolicy {
SOURCE, //在编译时会被丢弃,仅仅在源码中存在
CLASS, //默认策略,运行时就会被丢弃,仅仅在 class 文件中
RUNTIME //编译时会将注解信息记录到class文件,运行时任然保留,可以通过反射获取注解信息
}
编译时注解和运行时注解除了上面的区别之外,实现方式也不一样,编译时注解一般是通过注解处理器(APT)来实现的,运行时注解一般是通过反射来实现的。关于注解可以参考下面这篇文章:
什么是APT
APT(Annotation Processing Tool)是 javac 提供的一种可以处理注解的工具,用来在编译时扫描和处理注解的,简单来说就是可以通过 APT 获取到注解及其注解所在位置的信息,可以使用这些信息在编译器生成代码。编译时注解就是通过 APT 来通过注解信息生成代码来完成某些功能,典型代表有 ButterKnife、Dagger、ARouter等。
AbstractProcessor
AbstractProcessor
实现 Processor
是注解处理器的抽象类,要实现注解处理器都需要继承 AbstractProcessor
进行扩展,主要方法说明如下:
init:初始化Processor,可从参数
ProcessingEnvironment
中获取工具类Elements
、Types
、Filer
以及Messager
等;getSupportedSourceVersion:返回使用的 Java 版本;
getSupportedAnnotationTypes:返回要处理的的所有的注解名称;
process:获取所有指定的注解进行处理;
process 方法可能会在运行过程中执行多次,知道没有其他类产生为止。
Element和Elements
Element
类似于 XML 中的标签一样,在 Java 中Element
表示程序元素,如类、成员、方法等,每个 Element
都表示仅表示某种结构,对 Element
对象类的操作,请使用 visitor 或 getKind( 方法进行判断再具体处理,Element
的子类如下:
ExecutableElement
PackageElement
Parameterizable
QualifiedNameable
TypeElement
TypeParameterElement
VariableElement
上述元素结构分别对应下面代码结构:
// PackageElement
package manu.com.compiler;
// TypeElement
public class ElementSample {
// VariableElement
private int a;
// VariableElement
private Object other;
// ExecutableElement
public ElementSample() {
}
// 方法参数VariableElement
public void setA(int newA) {
}
// TypeParameterElement表示参数化类型,用在泛型参数中
}
Elements
是处理 Element
的工具类,只提供接口,具体有 Java 平台实现。
自定义编译注解处理器
下面使用 APT 实现一个注解 @Bind
来替换来模仿 ButterKnife 的注解 @BindView
,案例项目结构如下:
在 api 模块中定义注解 @Bind
如下:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface Bind {
int value();
}
compiler 模块是 Java 模块,引入 Google 的 auto-service 用来生成 META-INFO 下的相关文件,javapoet 用于更方便的创建 Java 文件:
// 用来生成META-INF/services/javax.annotation.processing.Processor文件
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
// 用于创建Java文件
implementation 'com.squareup:javapoet:1.12.1'
自定义注解处理器 BindProcessor
如下:
/**
* BindProcessor
*/
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {
private Elements mElements;
private Filer mFiler;
private Messager mMessager;
// 存储某个类下面对应的BindModel
private Map<TypeElement, List<BindModel>> mTypeElementMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mMessager = processingEnvironment.getMessager();
print("init");
// 初始化Processor
mElements = processingEnvironment.getElementUtils();
mFiler = processingEnvironment.getFiler();
}
@Override
public SourceVersion getSupportedSourceVersion() {
print("getSupportedSourceVersion");
// 返回使用的Java版本
return SourceVersion.RELEASE_8;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
print("getSupportedAnnotationTypes");
// 返回要处理的的所有的注解名称
Set<String> set = new HashSet<>();
set.add(Bind.class.getCanonicalName());
set.add(OnClick.class.getCanonicalName());
return set;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
print("process");
mTypeElementMap.clear();
// 获取指定Class类型的Element
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Bind.class);
// 遍历将符合条件的Element存储起来
for (Element element : elements) {
// 获取Element对应类的全限定类名
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
print("process typeElement name:"+typeElement.getSimpleName());
List<BindModel> modelList = mTypeElementMap.get(typeElement);
if (modelList == null) {
modelList = new ArrayList<>();
}
modelList.add(new BindModel(element));
mTypeElementMap.put(typeElement, modelList);
}
print("process mTypeElementMap size:" + mTypeElementMap.size());
// Java文件生成
mTypeElementMap.forEach((typeElement, bindModels) -> {
print("process bindModels size:" + bindModels.size());
// 获取包名
String packageName = mElements.getPackageOf(typeElement).getQualifiedName().toString();
// 生成Java文件的文件名
String className = typeElement.getSimpleName().toString();
String newClassName = className + "_ViewBind";
// MethodSpec
MethodSpec.Builder constructorBuilder = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(ClassName.bestGuess(className), "target");
bindModels.forEach(model -> {
constructorBuilder.addStatement("target.$L=($L)target.findViewById($L)",
model.getViewFieldName(), model.getViewFieldType(), model.getResId());
});
// typeSpec
TypeSpec typeSpec = TypeSpec.classBuilder(newClassName)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(constructorBuilder.build())
.build();
// JavaFile
JavaFile javaFile = JavaFile.builder(packageName, typeSpec)
.addFileComment("AUTO Create")
.build();
try {
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
});
return true;
}
private void print(String message) {
if (mMessager == null) return;
mMessager.printMessage(Diagnostic.Kind.NOTE, message);
}
}
其中 BindModel
是对注解 @Bind
信息的简单封装,如下:
/**
* BindModel
*/
public class BindModel {
// 成员变量Element
private VariableElement mViewFieldElement;
// 成员变量类型
private TypeMirror mViewFieldType;
// View的资源Id
private int mResId;
public BindModel(Element element) {
// 校验Element是否是成员变量
if (element.getKind() != ElementKind.FIELD) {
throw new IllegalArgumentException("element is not FIELD");
}
// 成员变量Element
mViewFieldElement = (VariableElement) element;
// 成员变量类型
mViewFieldType = element.asType();
// 获取注解的值
Bind bind = mViewFieldElement.getAnnotation(Bind.class);
mResId = bind.value();
}
public int getResId(){
return mResId;
}
public String getViewFieldName(){
return mViewFieldElement.getSimpleName().toString();
}
public TypeMirror getViewFieldType(){
return mViewFieldType;
}
}
在 bind 模块使用反射创建要生成的文件如下:
/**
* 初始化
*/
public class BindKnife {
public static void bind(Activity activity) {
// 获取activity的全限定类名
String name = activity.getClass().getName();
try {
// 反射创建并注入Activity
Class<?> clazz = Class.forName(name + "_ViewBind");
clazz.getConstructor(activity.getClass()).newInstance(activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
当然 ButterKnife 应该是把创建的对象缓存起来,不会每次都会去创建新的对象,虽然也是用了反射,但是相较运行时注解来说反射的测试减少了很多,所以性能也较运行时注解更好,这也算是编译时注解和运行时注解的区别。
使用自定义注解处理器
使用方式和 ButterKnife 类似,如下:
public class MainActivity extends AppCompatActivity{
@Bind(R.id.tvData)
TextView tvData;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BindKnife.bind(this);
tvData.setText("data");
}
}
推荐阅读: